查看原文
其他

Java 微服务框架新选择:Spring 5

2017-04-21 Alexey Nesterov 高可用架构

在这篇文章中,我们将讨论即将发布的第五代 Spring 框架中的新概念—— “Functional Web Framework”,来看看它如何帮助我们构建轻量级的微服务。

你可能会对标题中出现的 “Spring” 和 “微服务框架”感到惊讶。但是确实如此,Spring 5非常适合成为你 Java Web 微服务框架的新选择。首先,为了避免混淆让我们来对“微服务”中的“微”达成共识:


  • 简洁 - 无需样板工程,无需额外设置

  • 简单 - 没有任何“魔法”

  • 易于部署 - 产生单一的可部署工件

  • 容易运行 - 没有额外的依赖

  • 轻量级 - 最小内存占用/CPU 使用

  • 非阻塞 - 更好的并发性


虽然 Spring Boot 已经能做到上述的一些点,但 Spring MVC 本身依然引入了很多魔法。比如 @Controller 这个广泛使用的注解就有点含糊不清,更不用说 Spring 的自动配置和组件扫描等特性了。通常来说,这是开发一个大规模应用时可以承受的烦恼,毕竟 Spring 帮我们搞定了依赖注入、请求路由、各类复杂的配置等。然而,在微服务的世界中,应用程序只是类似一个大机器中运行的小齿轮,Spring Boot 就显得有些“杀鸡用牛刀”了。


为了解决这些问题,Spring 团队引出了一个名为“Functional Web Framework”的新概念,它是 Spring WebFlux(以前称为 Spring Reactive Web)这个大项目的一部分。同时也是我们现在要讨论的。


首先,让我们回顾一下基础知识,看看一个 Web 应用程序到底是什么样的,以及由什么组件构成。很显然,最基本的部分就是网络服务器(Web server)本身,为了避免手工解析 HTTP 请求,然后委派给应用程序的某个方法,我们需要一个请求路由器(router),同时我们也需要一个请求处理器处理程序(handler),其实就是一段代码,它可以接受请求,做实际的逻辑处理,并最终返回一个响应。所有这些也正是 Spring Functional Web 所做的,它剥离了所有的抽象层(beans 和 contexts)。注意,这并不意味着它脱离并放弃了成熟的 Spring MVC 模型,而是提供了使用 Spring 来构建 Web 应用程序的另一种选择。


请求处理器


我们来看一下这个例子。 开始前请访问 使用项目创建器创建一个新的空白工程,使用 Spring Boot 2.0 和 Reactive Web 作为唯一的依赖。 接着我们就可以定义第一个请求处理器或处理方法(handler)了,很简单,它接受请求并返回响应。



从上述代码可以看出来,它是 HandlerFunction 接口的一个实现,定义了一个方法来获取一个请求(类型为 ServerRequest),并返回具有 "Hello" 字符串的 ServerResponse 对象。 Spring 还提供了方便的构建器(builder)来构造响应。在我们的例子中,我们使用 ok() 自动将返回码设置为 HTTP 200 。为了构造响应体,我们使用另一个叫 Mono 的概念,它代表 single reactive value ,但我们这里先不管它,只要明白 Mono.just(...) 是一种通过返回 Publisher 类型对象(其实是类似 Promise)来实现非阻塞编程范式的方式。Reactive Web 是Spring 5 的一部分,它是通过 Java 9 的 Reactive Stream 来实现的。你可以参考 Dave Syer 的。

我们还可以使用 Java 8 的 lambdas 表达式使代码更简洁,如下:



请求路由器


上面我们已经有一个 handler 了,现在我们可以定义一个请求路由器了。 假设我们要使用 GET 方法请求 "/" 时调用我们的 handler。 为此,我们可以使用 RouterFunction



routeGET 都是 RequestPredicatesRouterFunctions 的静态方法,它们可以用来构建 RouterFunction 。它接受一个请求,检查它是否能匹配现有handler(比如请求路径(path)、请求方法(method)或者是内容类型(content type)等)。如果匹配则调用 handler。 在我们的例子中,HTTP 方法是 GET,请求路径是 "/", handler 函数是上面定义的 hello


Web 服务器


现在我们可以把他们组装在一起来完成整个应用程序。我们将使用非常轻量、简单的 Reactive Netty 作为 Web 服务器。要将我们的请求路由器集成到 Web 服务器中,我们需要将其转换为 HttpHandler



接着这样来启动 Web 服务器:



其中 ReactorHttpHandlerAdapter 只是一个包装了 HttpHandler 的 Netty 中的类,其余的代码非常简单直白。我们创建一个新的 Web 服务器,监听 localhost 地址的8080端口,并且添加我们的 HTTP handler,实际上这是我们的请求路由器的入口。


好了!整个应用程序已经差不多了,完整的代码如下:



最后一行只是用来保持 JVM 进程一直运行。 你可能会发现整个应用程序启动飞快,这是因为没有任何组件扫描或配置注入发生,就像以前你们使用 Spring 会遇到的。

同时整个程序可以作为一个简单的 Java 应用程序来运行,不需要任何容器。

为了将整个应用打包和部署,我们仍然可以利用 Spring Maven 插件,只需执行以下操作:



此命令将生成一个包含所有依赖关系的 fat jar,可以在仅安装了 JRE 的环境来部署和执行:



另外,如果我们想查看整个应用的内存使用情况,大概只有32MB左右,包括了22MB的 metaspace(你们知道用来存放加载的 classes)和大约10MB的 heap。就像前面提到的,整个框架和运行时环境只需要很少的资源。


支持JSON


在上面的示例中,我们返回一个字符串作为响应,但是想返回 JSON 对象也非常容易。

让我们创建一个新的可以返回 JSON 的 API endpoint 来扩展我们的应用。这个 data class 非常简单,只有一个名为 name 的字符串类型的字段。为了避免写那些冗长的 Java 样板代码(就像你们厌恶的 setter, getter),我们使用 特性:使用 @Data 注解。通过在类上添加此注解,我们可以透明得获得 gettersettersequalshashCode 方法,而无须手动实现。



然后,我们需要扩展我们的请求路由器,以便为 "/json" 路径的 GET 请求提供新的响应。 这可以通过在现有路由上调用 andRoute(...) 方法来完成。



我们还优化了一点代码,将新的返回 JSON 的 handler 以内连的方式声明,同时将 ok() 静态导入,这使得代码变得更简洁。

重新启动后,应用程序将通过 "/json" 路径返回 {"name": "world"},同时将响应头部中的内容类型(content-type)设置为 application/json


应用上下文


你可能已经注意到整个代码中并没有定义应用上下文(Application Context)。是的,我们不再需要它!Spring WebFlux 中支持 RouterFunction,这样一个简单且轻量的 JSON 服务不再需要应用上下文。

测试


为了测试 reactive web application,Spring 提供了新的名为 WebTestClient 的客户端(类似于 MockMvc)。 我们将它绑定到我们的请求路由器上:



WebTestClient 有一组针对返回结果的断言,以验证返回状态码,返回体,以及内容类型等等。


总结


Spring 5 引入了新的编程范式用来开发小型的、轻量级的、微服务式 Web应用程序。 我们显式得定义请求路由器和请求处理函数,在完全不需要应用上下文的情况下快速运行并部署。

代码


本文中所有代码均可以在访问到。

https://github.com/alek-sys/spring-functional-microframework


参考


  • https://spring.io/blog/2016/09/22/new-in-spring-5-functional-web-framework

  • https://spring.io/blog/2016/06/13/notes-on-reactive-programming-part-ii-writing-some-code

  • http://www.baeldung.com/spring-5-functional-web

  • https://spring.io/blog/2016/07/28/reactive-programming-with-spring-5-0-m1


推荐阅读



本文作者 Alexey Nesterov,由魏佳翻译,转载请注明出处,技术原创及架构实践文章,欢迎通过公众号菜单「联系我们」进行投稿。


高可用架构

改变互联网的构建方式


长按二维码 关注「高可用架构」公众号



您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存